package org.stayfool.auth.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.stayfool.auth.common.service.BaseServiceImpl;
import org.stayfool.auth.constants.PermissionType;
import org.stayfool.auth.constants.ResourceType;
import org.stayfool.auth.entity.auth.AppResource;
import org.stayfool.auth.entity.auth.AppUser;
import org.stayfool.auth.entity.auth.ResourcePermissionMap;
import org.stayfool.auth.entity.auth.ResourceRoleMap;
import org.stayfool.auth.vo.ResourceTree;

/**
 * @author jiangych
 */
@Service("authService")
@Transactional(rollbackFor = Throwable.class)
public class AuthServiceImpl extends BaseServiceImpl implements AuthService {

	// load url authorization
	public Map<String, String> getResourceAuthorization() {

		Map<String, String> result = new HashMap<>();

		Map<String, String> permMap = getPermission();
		Map<String, String> roleMap = getRole();

		// if url has both role and permission , merge it
		Iterator<Entry<String, String>> it = roleMap.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, String> roleEntry = it.next();
			if (permMap.containsKey(roleEntry.getKey())) {
				StringBuilder authBuilder = new StringBuilder();
				authBuilder.append(roleEntry.getValue());
				authBuilder.append(",");
				authBuilder.append(permMap.get(roleEntry.getKey()));

				result.put(roleEntry.getKey(), authBuilder.toString());

				// remove from both roleMap and permMap
				it.remove();
				roleMap.remove(roleEntry.getKey());
			}
		}

		// put rest of role into result
		result.putAll(roleMap);
		// put rest of permission into result
		result.putAll(permMap);

		return result;
	}

	// load url permission
	private Map<String, String> getPermission() {

		Map<String, String> result = new HashMap<>();

		StringBuilder hql = new StringBuilder();
		hql.append(" select distinct rp from ResourcePermissionMap rp ");
		hql.append(" left join fetch rp.resource as r left join fetch rp.permission as p ");
		hql.append(" where rp.isDeleted = false and r.isDeleted = false and p.isDeleted = false ");

		List<ResourcePermissionMap> permList = find(hql.toString());
		Map<String, Set<String>> permMap = new ConcurrentHashMap<>();

		permList.parallelStream().forEach(rp -> {
			String url = rp.getResource().getUrl();
			String perm = rp.getPermission().getCode();

			if (permMap.containsKey(url))
				permMap.get(url).add(perm);
			else
				permMap.put(url, new ConcurrentSkipListSet<>(Arrays.asList(perm)));
		});

		Iterator<Entry<String, Set<String>>> it = permMap.entrySet().iterator();

		while (it.hasNext()) {
			StringBuilder perm = new StringBuilder();
			StringBuilder permBuilder = new StringBuilder();
			Entry<String, Set<String>> entry = it.next();
			entry.getValue().stream().forEach(p -> {

				// special permission
				if (PermissionType.contaions(p.trim().toUpperCase())) {
					perm.append(p.trim() + ",");
				}

				// custom
				else {
					permBuilder.append("\"").append(p.trim()).append("\",");
				}
			});

			// remove ',' if contains
			if (perm.length() > 0 && permBuilder.length() == 0) {
				perm.deleteCharAt(perm.lastIndexOf(","));
			}

			// merge permissions
			if (permBuilder.length() > 0) {
				permBuilder.deleteCharAt(permBuilder.lastIndexOf(","));
				perm.append("perms[").append(permBuilder.toString()).append("]");
			}

			if (perm.length() > 0)
				result.put(entry.getKey(), perm.toString());
		}

		return result;
	}

	// load url role
	private Map<String, String> getRole() {

		Map<String, String> result = new HashMap<>();

		StringBuilder hql = new StringBuilder();
		hql.append(" select distinct rr from ResourceRoleMap rr ");
		hql.append(" left join fetch rr.resource as res left join fetch rr.role as role ");
		hql.append(" where rr.isDeleted = false and res.isDeleted = false and role.isDeleted = false ");

		List<ResourceRoleMap> roleList = find(hql.toString());

		Map<String, Set<String>> roleMap = new ConcurrentHashMap<>();

		roleList.parallelStream().forEach(rr -> {
			String url = rr.getResource().getUrl();
			String role = rr.getRole().getCode();

			if (roleMap.containsKey(url))
				roleMap.get(url).add(role);
			else
				roleMap.put(url, new ConcurrentSkipListSet<>(Arrays.asList(role)));
		});

		Iterator<Entry<String, Set<String>>> it = roleMap.entrySet().iterator();

		while (it.hasNext()) {
			StringBuilder roleStr = new StringBuilder();
			StringBuilder roleBuilder = new StringBuilder();
			Entry<String, Set<String>> entry = it.next();
			entry.getValue().stream().forEach(p -> roleBuilder.append("\"").append(p.trim()).append("\","));

			// merge roles
			if (roleBuilder.length() > 0) {
				roleBuilder.deleteCharAt(roleBuilder.lastIndexOf(","));
				roleStr.append("roles[").append(roleBuilder.toString()).append("]");
			}

			if (roleStr.length() > 0)
				result.put(entry.getKey(), roleStr.toString());
		}

		return result;
	}

	// find user by username
	public AppUser getUserByName(String userName) {
		String hql = "from AppUser where userName = ? and isDeleted = false";
		return findUnique(hql, new Object[] { userName });
	}

	// load permission
	public List<String> getPermissions(String userName) {
		StringBuilder hql = new StringBuilder();
		hql.append(" select p.permission.code from UserPermissionMap p ");
		hql.append(" where p.isDeleted = false and p.permission.isDeleted = false and p.user.isDeleted = false ");
		hql.append(" and p.user.userName = ? ");

		List<Object[]> permsList = findObj(hql.toString(), new Object[] { userName });
		Set<String> permsSet = new ConcurrentSkipListSet<>();
		permsList.parallelStream().forEach(objs -> permsSet.add(((Object) objs).toString()));

		return new ArrayList<>(permsSet);
	}

	// load roles
	public List<String> getRoles(String userName) {
		StringBuilder hql = new StringBuilder();
		hql.append(" select r.role.code from UserRoleMap r ");
		hql.append(" where r.isDeleted = false and r.role.isDeleted = false and r.user.isDeleted = false ");
		hql.append(" and r.user.userName = ? ");

		List<Object[]> rolesList = findObj(hql.toString(), new Object[] { userName });
		Set<String> rolesSet = new ConcurrentSkipListSet<>();
		rolesList.parallelStream().forEach(objs -> rolesSet.add(((Object) objs).toString()));

		return new ArrayList<>(rolesSet);
	}

	// load navigation
	public List<ResourceTree> getNavigation(String userName) {

		StringBuilder hql = new StringBuilder();
		hql.append(" select distinct r from AppResource r ");
		hql.append(" left join r.permissionMaps rpm ");
		hql.append(" left join rpm.permission p ");
		hql.append(" left join p.userMaps upm ");
		hql.append(" left join upm.user u ");

		hql.append(" where r.isDeleted = false and r.type = ? and ");
		hql.append(" u.username = ? ");
		// hql.append(" r.permissionMaps.permission.uuid in ");
		// hql.append(" (select upm.permission.uuid from UserPermissionMap upm
		// where upm.user.username = ? ) ");
		// hql.append(" or r.roleMaps.role.uuid in ");
		// hql.append(" (select urm.role.uuid from UserRoleMap urm where
		// urm.user.username = ? )");
		// hql.append(" )");

		List<AppResource> resources = find(hql.toString(), new Object[] { ResourceType.NAVIGATION.name(), userName });
		List<ResourceTree> result = new ArrayList<>(resources.size());

		resources.stream().forEach(r -> result.add(convert(r)));

		return result;
	}

	private ResourceTree convert(AppResource r) {
		ResourceTree t = new ResourceTree();
		try {
			BeanUtils.copyProperties(t, r);
			r.getChildren().stream().forEach(sub -> t.getSubResource().add(convert(sub)));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return t;
	}

	/**
	 * 把URL保存成resource，存在则放弃
	 * 
	 * @param url
	 */
	public void saveIfAbsent(String url) {

		String hql = "select count(uuid) from AppResource where isDeleted = ? and url = ?";
		Long count = findUnique(hql, new Object[] { false, url });
		if (count > 0)
			return;

		AppResource r = new AppResource();
		r.setUrl(url);

		save(r);
	}
}
